#ifndef vmidi_impl_common_h
#define vmidi_impl_common_h

#include "vmidi_interface.h"

#include <map>
#include <set>
#include <list>
#include <sstream>
using namespace std;

namespace VMidi {

// First identifier to use for a port.
#define FIRST_ID (1)

class VMidiImplCommon: public VMidiInterface {
  public:
    /**
      * Debug (dump) contents.
      */
    void dump ();

  protected:
    inline VMidiImplCommon ();
    /**
      * Clears everything up.
      * WARNING : the port data is not destroyed automatically
      * because some library may not tolerate it, so the code is not intrusive.
      * @sa getInputs
      * @sa getOutputs
      */
    inline ~VMidiImplCommon ();
  
    /**
      * @param port Implementation-dependent port data to add to the list of
      * inputs or outputs.
      * @param type type of ports : input or output.
      * @return the port's unique identifier.
      * @sa VMidiPortType
      */
    int addPort (void* port, VMidiPortType type);  

    /**
      * Removes a port from the list along with all its connections.
      * @param id Id of the port to remove.
      * @param type type of port : input or output.
      * @return the removed port. Practical for external deleting.
      * @sa VMidiPortType
      */
    void* removePort (const int id, VMidiPortType type) throw(string);

    /**
      * Should be used only for outside destruction.
      * @param ports list of the ports.
      * @param type type of port : input or output.
      * @sa VMidiPortType
      */
    void  getPorts (list<void*>& ports, VMidiPortType type) {
      if (type == VMIDI_PORT_INPUT) {
        mapAsList(ports, inputs);
      }
      else if (type == VMIDI_PORT_OUTPUT) {
        mapAsList(ports, outputs);
      }
      else {
        ports.clear();
      }
    }

    /**
      * @return the port data, given its id. No check is performed.
      */
    void* getInputPortData (int portId) {
      return inputs.find(portId)->second;
    }

    /**
      * @return the port data, given its id. No check is performed.
      */
    void* getOutputPortData (int portId) {
      return outputs.find(portId)->second;
    }

    /**
      * Should be used only for outside destruction.
      * @param outputs_list the list of outputs.
      */
    inline
    void getOutputs(list<void*>& outputs_list) {
      mapAsList(outputs_list, outputs);
    }

    /**
      * Allows to activate or deactive the current instance of VMidi.
      * If not active, all the declared port connexions will be inactive.
      */
    void setActive (bool state) {
      active = state;
    }

    /**
      * Connects an input port to an output port.
      * After the connection is established, every information coming into
      * @p srcPortId will be redirected to port @dstPortId. This is
      * called MIDI Thru.
      * @param srcPortId source port identifier.
      * @param dstPortId destination port identifier.
      * @throw An error string in case of error in the implementation.
     */
    virtual void connectPorts (int srcPortId, int dstPortId) throw(string);

    /**
     * Disconnects the provided ports so that VMidi will not perform any MIDI
     * thru between them anymore.
     * @throw An error string in case of error in the implementation.
     */
    virtual void disconnectPorts (int srcPortId, int dstPortId) throw(string);

    /**
      * Finalizes the VMidi system.
      * This function will close the underlying MIDI API and free the
      * list of declared MIDI ports (both real and virtual).
      * @throw An error string in case of error in the implementation.
      */
    virtual void finalize () throw(string);

    /**
      * @return true if the \inputPortId has no output port connected.
      */
    bool isOrphan (int inputPortId) throw(string) {
      set<int>* outputs = getOutputs(inputPortId);
      return (outputs == NULL) || (outputs->empty());
    }

    /**
      * @return the list of outputs connected to the specified input.
      */
    set<int>* getOutputs (int inputPortId) throw(string) {
      checkPortId(VMIDI_PORT_INPUT, inputPortId);

      map< int,set<int>* >::iterator it = connections.find(inputPortId);

      if (it != connections.end()) {
        return it->second;
      }

      return NULL;
    }
  
    /**
      * @param inputs the list of inputs connected to the specified output.
      */
    void getInputs (set<int>& inputs, int outputPortId) throw(string) {
      checkPortId(VMIDI_PORT_OUTPUT, outputPortId);

      for (map< int, set<int>* >:: iterator c_itr = connections.begin();
           c_itr != connections.end();
           ++c_itr) {
        int input = c_itr->first;
        set<int>* outputs = c_itr->second;
      
        // The output is in the list of outputs connected to the input.
        if (outputs->find(outputPortId) != outputs->end()) {
          inputs.insert(input);
        }
      }
    }

  private:
    /**
      * @return the next valid identifier.
      */
    inline int getId ();
    
    /**
      * @param values the list of values of @p map_p.
      */
    inline void mapAsList (list<void*>& values, map<int, void*> map_p);

    /**
      * @brief checks if the specified port exists.
      */
    inline void checkPortId (VMidiPortType type, int id) throw(string);

  private:
    map<int, void*> inputs;
    map<int, void*> outputs;
    map< int,set<int>* > connections;

    int nextId;
    list<int> idPool;

    bool active;
};

VMidiImplCommon::
VMidiImplCommon () : nextId(FIRST_ID), active(true) {
  idPool.clear();
}

VMidiImplCommon::
~VMidiImplCommon () {
}

void VMidiImplCommon::
mapAsList(list<void*>& values, map<int,void*> map_p) {
  for (map<int,void*>::iterator it = map_p.begin(); it != map_p.end(); ++it) {
    values.push_back(it->second);
  }
}



int VMidiImplCommon::
getId () {
  if (idPool.empty()) {
    return nextId++;
  }
  else {
    // Find the next available id in the pool.
    int id = idPool.front();
    // Remove it from the pool.
    idPool.pop_front();
    // Return it.
    return id;
  }
}

void VMidiImplCommon::
checkPortId (VMidiPortType type, int id) throw(string) {
  
  if (type == VMIDI_PORT_INPUT  && inputs.find(id) == inputs.end()) {
    stringstream ss;
    ss << __FILE__ << "@" << __LINE__ << " : no such input port : " << id;
    throw ss.str();
  }
  if (type == VMIDI_PORT_OUTPUT  && outputs.find(id) == outputs.end()) {
    stringstream ss;
    ss << __FILE__ << "@" << __LINE__ << " : no such output port : " << id;
    throw ss.str();
  }

}

};

#endif

